const MD5 = require('md5');
const $_ = require('lodash');

// websocket.readyState
// 0 (WebSocket.CONNECTING) 正在链接中
// 1 (WebSocket.OPEN) 已经链接并且可以通讯
// 2 (WebSocket.CLOSING) 连接正在关闭
// 3 (WebSocket.CLOSED) 连接已关闭或者没有链接成功
let WSK = function (opts) {
  this.name = opts.name || '1';
  this.host = opts.host || "ws://127.0.0.1";
  this.isAutoLink = $_.isBoolean(opts.isAutoLink) ? opts.isAutoLink : false; //允许断线重连

  //指定发包开头标识，默认为<BEGIN>，   空str:true
  //指定心跳包字符串，默认为__HEART__， 空str:false
  //指定发包结尾标识，默认为<END>，     空str:flase
  this.strBegin = $_.isString(opts.strBegin) ? opts.strBegin : '<BEGIN>';
  this.strHeart = ($_.isString(opts.strHeart) && Boolean(opts.strHeart)) ? opts.strHeart : '__HEART__';
  this.strEnd = ($_.isString(opts.strEnd) && Boolean(opts.strEnd)) ? opts.strEnd : '<END>';

  this.timeHeart = opts.timeHeart || 1000 * 60;//心率
  this.wsObj = opts.wsObj || {};
  this.MD5Key = opts.MD5Key || '__Date2MD5';
  this.autoLinkMax = opts.autoLinkMax || 65535;

  this.socket = null;
  /*4个事件+前后共8个回调函数*/
  this.beforeOpen = $_.isFunction(opts.beforeOpen) ? opts.beforeOpen : () => { };
  this.afterOpen = $_.isFunction(opts.afterOpen) ? opts.afterOpen : () => { };
  this.beforeClose = $_.isFunction(opts.beforeClose) ? opts.beforeClose : () => { };
  this.afterClose = $_.isFunction(opts.afterClose) ? opts.afterClose : () => { };
  this.beforeError = $_.isFunction(opts.beforeError) ? opts.beforeError : () => { };
  this.afterError = $_.isFunction(opts.afterError) ? opts.afterError : () => { };
  this.beforeMessage = $_.isFunction(opts.beforeMessage) ? opts.beforeMessage : () => { };
  this.afterMessage = $_.isFunction(opts.afterMessage) ? opts.afterMessage : () => { };
  this.beforeSendHeart = $_.isFunction(opts.beforeSendHeart) ? opts.beforeSendHeart : () => { };
  this.afterGetHeart = $_.isFunction(opts.afterGetHeart) ? opts.afterGetHeart : () => { };
  /*调用时都传入两个参数：1、当前对象obj；2、当前事件event*/
  return this;
}

function handleOpen($event) {
  this.__autoLink_num = 0;
  clearTimeout(this.__autoLink_t);
  //重置自动重连的计数器
  this.send(this.wsObj);
  let self = this;
  setTimeout(function () { self._$heart(true); }, this.timeHeart)//延迟一个周期启动心跳
}
function isHeart(str) {
  return str === `${this.strBegin}${this.strHeart}${this.strEnd}`;
}
function sortingAbnormal(str) {//sorting:分拣 abnormal:异常
  //没有异常返回 0 ，异常返回{message:errorMsg,strMsg}
  let α = this.strBegin;
  let Ω = this.strEnd;
  if (Boolean(α) && (str.indexOf(α) == 0)) {
    return { type: 1, message: '消息不以约定的消息头开始', json: null }
  }
  if (str.lastIndexOf(Ω) != (str.length - Ω.length)) {
    return { type: 2, message: '消息不以约定的消息尾结束', json: null }
  }
  let json;
  try {
    json = JSON.parse(str.substring(α.length, str.lastIndexOf(Ω)))
  } catch (err) {
    console.error(err);
    return { type: 4, message: '消息主体无法解析为JSON格式', json: null };
  }
  return { type: 0, json: json };
}
function handleMessage($event, msg) {
}
function handleClose($event) {
}
function handleError($event) {
  this._$autoLink();//尝试自动重连
}

WSK.prototype = {
  __tmpSend: [],//发送缓存队列
  __tmpReceive: '',//接受缓存队列(实际是字符串)
  __autoLink_t: {},//--重试定时器
  __autoLink_num: 0,//--重复计数器--
  start: function () {
    let self = this;
    this.socket = new WebSocket(this.host);

    this.socket.onopen = function (e) {
      self.beforeOpen(self, e);
      handleOpen.call(self, e)
      self.afterOpen(self, e);
    }
    this.socket.onclose = function (e) {
      // self.beforeClose(self, e)
      self._$autoLink();//尝试自动重连
      // handleClose.call(self, e)
      // self.afterClose(self, e)
    }
    this.socket.onmessage = function (e) {
      let strMsg = e.data;
      new Promise((res, rej) => {
        let r = self.beforeMessage(self, strMsg, e)
        res(r);//未经过心跳分拣的
      }).then((res) => {
        if (isHeart.call(self, res)) {
          self.afterGetHeart(self);
          return Promise.resolve({ type: 'heart' });
          //心跳可能性较高，提前处理直接返回指定值，后续检测指定值&&不执行afterMessage
        } else {
          let r = sortingAbnormal.call(self, strMsg);
          if (r.type != 0) {
            return Promise.reject(r)
          } else {
            return Promise.resolve(r);
          }
        }
      }).then((res) => {
        if (res.type === 'heart') { } else {
          handleMessage.call(self, e, res.json);
          self.afterMessage(self, res.json, e);
        }
      });
    }
    this.socket.onerror = function (e) {
      // self.beforeError(self, e);
      self._$autoLink();//尝试自动重连
      handleError.call(self, e)
      // self.afterError(self, e);
    }
    return this;
  },//start
  stop: function (e) {
    let self = this;
    this.isAutoLink = false;
    //手动终止自动重连
    //清空队列
    this.__tmpSend = [];
    this.__tmpReceive = [];
    // self.beforeClose(self, e);
    try {
      this.socket.close();
    } catch (err) {
      console.error(err);
    }
    // self.afterClose(self, e);
    return this;
  },//stop
  send: function (msgObj) {
    //指定的信息使用websocket对象原生send发送
    //未指定的信息使用封装的方式发送
    //按要求还要规避粘包，引入__tmp
    if ($_.isNil(msgObj)) { return false };
    let self = this;
    let msg = '';

    // self.wsObj[] = ;
    try {
      let obj = Object.assign(
        self.wsObj,
        msgObj,
      )
      $_.set(obj, self.MD5Key, MD5(new Date()))
      msg = self.strBegin + JSON.stringify(obj) + self.strEnd;
    } catch (err) {
      msg = msgObj.toString()
    }
    this.__tmpSend.push(msg);//缓存队列增加信息列表
    // 数组去重
    for (var i = 0; i < this.__tmpSend.length; i++) {
      for (var j = i + 1; j < this.__tmpSend.length; j++) {
        let data_1 = this.__tmpSend[i].split('<EOF>')[0]
        data_1 = JSON.parse(data_1)
        let data_2 = this.__tmpSend[j].split('<EOF>')[0]
        data_2 = JSON.parse(data_2)
        if (data_1.msgBody == data_2.msgBody) {         //第一个等同于第二个，splice方法删除第二个
          this.__tmpSend.splice(j, 1);
          j--;
        }
      }
    }

    if (self.__t === undefined) {//这个变量没有声明过……
      self.__t = setInterval(function () {
        // https://developer.mozilla.org/zh-CN/docs/Web/API/WebSocket/bufferedAmount
        if (self.socket.bufferedAmount == 0) {//要求发送前bufferedAmount必须为空
          if (self.__tmpSend.length > 0) {//缓存队列尚有未发消息
            try {//尝试发送消息
              self.socket.send(self.__tmpSend[0]);
            } catch (err) {
              console.error(err)
            }
            self.__tmpSend.splice(0, 1);//移除已发送
          } else {//缓存队列以空
            clearInterval(self.__t);
            self.__t = undefined;
            //清空定时器，判断条件复位
          }
        }
      }, 20)
    }
    return this;
  },//send
  _$heart: function (boolean) {
    let self = this;
    if ([2, 3].includes(this.socket.readyState)) {
      return false
    }//既然已经关闭了就没必要发心跳包
    if (boolean) {
      this.beforeSendHeart(this);
      let heartBox = `${this.strBegin}${this.strHeart}${this.strEnd}`
      this.socket.send(heartBox)//指定的心跳标识和结束标识
    }
    setTimeout(
      function () { self._$heart(true) },
      self.timeHeart
    )
    return this;
  },//心跳包
  _$autoLink: function () {
    let self = this;
    console.log(self.__autoLink_num, self.autoLinkMax, self.__autoLink_num > self.autoLinkMax)

    if (this.isAutoLink) {
      self.__al_time = self.__al_time ? self.__al_time : 1000;//这个变量没有声明过……
      self.__autoLink_t = setTimeout(function () {
        self.__autoLink_num++;
        if (self.__autoLink_num > 2) {
          self.__al_time = 5000;
        }
        if (self.__autoLink_num > self.autoLinkMax) {
          self.isAutoLink = false;
          console.log('放弃尝试....');
          self.socket.close();
          return false;
        }
        console.log(`[${self.__autoLink_num}/${self.autoLinkMax}]尝试重连...`);
        self.start();
      }, self.__al_time);
    } else {
      this.stop();
    }
  },//自动重连
}
export default WSK;
